/*++

Copyright (c) 2007  Microsoft Corporation 

Filename: 

    AddonWindowWrapper.cs

Abstract: 

    This file Wraps the anchor window implementation.
     
--*/
using System;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Visio = Microsoft.Office.Interop.Visio;

namespace NRTMAddIn
{
	/// <summary>
	/// Constants for Addon Windows in this application.
	/// </summary>
	public sealed class AddonWindowConstants
	{
		/// <summary>
		/// Default Constructor - private.
		/// </summary>
		private AddonWindowConstants()
		{
		}

		// constants
		public const string NrtmAddonWindowMergeId = "{B4393601-9309-4707-B159-D8DFA82D409F}";
	}


	/// <summary>
	/// Summary description for AddonWindowWrapper.
	/// </summary>
	public sealed class AddonWindowWrapper : Visio.IVisEventProc
	{
		#region WIN API Declares
         
		[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet=CharSet.Auto,
                    CallingConvention=CallingConvention.Winapi)]
		private static extern int SetWindowLong(IntPtr hWnd, int index, int newLong);

		[DllImport("user32.dll", EntryPoint = "SetParent", CharSet=CharSet.Auto,
                    CallingConvention=CallingConvention.Winapi)]
		private static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

		[DllImport("user32.dll", EntryPoint = "GetWindowRect", CharSet=CharSet.Auto, 
                    CallingConvention=CallingConvention.Winapi)]
		private static extern int GetWindowRect(IntPtr hWnd, ref RECT lpRect);

		[DllImport("user32.dll", EntryPoint = "SetWindowPos", CharSet=CharSet.Auto,
                    CallingConvention=CallingConvention.Winapi)]
		private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, 
                    int X, int Y, int cx, int cy, int wFlags);
               
		private const int GWL_STYLE = (-16);
		private const int WS_CHILD = 0x40000000;
		private const int WS_OVERLAPPED = 0x00000000;
		private const int SWP_NOCOPYBITS = 0x100;
		private const int SWP_NOMOVE = 0x2;
		private const int SWP_NOZORDER = 0x4;
		private const int GW_CHILD = 5;
		private const int GW_HWNDNEXT = 2;

		[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
			private struct RECT
		{
			public int left;
			public int top;
			public int right;
			public int bottom;
		}

		#endregion

		#region fields
		
		// privates

        //Visio window reference
		private Visio.Window m_visioWindow;
		//Form reference
        private Form m_form;
		//Flag to check when to destroy form
        private bool m_destroyFormOnClose = true;

		#endregion

		#region events/delegates

        //To handle document close
		public delegate void TerminateEventHandler();
		public event TerminateEventHandler Terminate;

		#endregion

		#region construction / destruction

		/// <summary>
		/// Default Constructor.
		/// </summary>
		public AddonWindowWrapper()
		{
		}


		/// <summary>
		/// Constructor - overridden.
		/// </summary>
		/// <param name="destroyFormOnClose">
		/// True = the form is close when the window is closed.
		/// False = the form is not closed when the window is closed.
		/// </param>
		public AddonWindowWrapper(
			bool destroyFormOnClose)
		{
			m_destroyFormOnClose = destroyFormOnClose;
		}


		#endregion

		#region properties

		/// <summary>
		/// Activate this window to make it have focus.
		/// </summary>
		public void Activate()
		{
			// activate this window
			if (m_visioWindow != null)
			{
				m_visioWindow.Activate();
			}		
		}


		/// <summary>
		/// Return or set the form class to show in this anchor window.
		/// </summary>
		public Form FormToShow
		{
			get { return m_form; }
			set { m_form = value; }
		}


		/// <summary>
		/// Get or set the Visio.Window object.
		/// </summary>
		public Visio.Window VisioWindow
		{
			get
            {
                return m_visioWindow;
            }

            set
            {
                if (value == null)
                    return;
                m_visioWindow = value;
                m_visioWindow.BeforeWindowClosed += new
                    Microsoft.Office.Interop.Visio.EWindow_BeforeWindowClosedEventHandler(OnBeforeWindowClosed);
            }
		}

		/// <summary>
		/// Set or return the caption for this window.
		/// </summary>
		public string WindowCaption
		{
			get
			{
				string retVal = string.Empty;

				if (m_visioWindow != null)
				{
					retVal = m_visioWindow.Caption;
				}

				return retVal;
			}

			set
			{
				if (m_visioWindow != null)
				{
					m_visioWindow.Caption = value;
				}
			}
		}


		/// <summary>
		/// Hide or show this window.
		/// </summary>
		public bool Visible
		{
			get
			{
				bool retVal = false;

				if (m_visioWindow != null)
				{
					retVal = m_visioWindow.Visible;
				}

				return retVal;
			}

			set
			{
				if (m_visioWindow != null)
				{
					m_visioWindow.Visible = value;

					if (value)
					{
						m_visioWindow.Activate();
					}
				}
			}
		}

		#endregion

		#region methods

		/// <summary>
		/// Close this window.
		/// </summary>
		public void DestroyWindow()
		{
			try
			{
				if (m_visioWindow != null &&
					m_form != null)
				{
					// get the handle of the form
					IntPtr childWindowHandle = m_form.Handle;

					// hide the form
					m_form.Hide();

					// return the form window back to a normal window without a parent
					SetWindowLong(childWindowHandle, GWL_STYLE, WS_OVERLAPPED);
					SetParent(childWindowHandle, (IntPtr)0);

					// close the window
					m_visioWindow.Close();
					m_visioWindow = null;
				}

				// do we need to destroy the form as well?
				if (m_form != null &&
					m_destroyFormOnClose)
				{
					m_form.Close();
					m_form.Dispose();
                    m_form = null;
				}
			}
			catch
			{
				//Ignore errors
			}

			// default return
			return;
		}


		/// <summary>
		/// Show this window.
		/// </summary>
		/// <param name="visioParentWindow">
		/// </param>
		/// <param name="WindowState">
		/// </param>
		/// <param name="HorizMult">
		/// </param>
		/// <param name="VertMult">
		/// </param>
		/// <returns>
		/// </returns>
		public Visio.Window ShowWindow(
			Visio.Window visioParentWindow,
			string mergeId,
			int position)
		{
			Visio.Window retVal = null;

			try
			{
                if (visioParentWindow == null)
                {
                    return null;
                }
                if (m_form != null && visioParentWindow != null)
                {
                    IntPtr childWindowHandle = m_form.Handle;

                    this.VisioWindow = visioParentWindow.Windows.Add(
                        Type.Missing,
                        (int)Visio.VisWindowStates.visWSDockedBottom | (int)Visio.VisWindowStates.visWSAnchorMerged,
                        Visio.VisWinTypes.visAnchorBarAddon,
                        0,
                        0,
                        200,
                        200,
                        mergeId, // this guid is the same thus each anchor window will dock together
                        string.Empty,
                        position);

                    // do not show the window till we host the form in it
                    m_visioWindow.Visible = false;

                    // host the form in the window
                    IntPtr parentWindowHandle = (IntPtr)m_visioWindow.WindowHandle32;

                    SetWindowLong(childWindowHandle, GWL_STYLE, WS_CHILD);
                    SetParent(childWindowHandle, parentWindowHandle);

                    m_form.Show();

                    JiggleWindow(parentWindowHandle);

                    // show the window
                    m_visioWindow.Visible = true;

                    // activate this window
                    this.Activate();

                    // set the return
                    retVal = this.VisioWindow;
                }
			}
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("ShowWindow", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("ShowWindow", ex);
            }

			// return success/failure
			return retVal;
		}

		/// <summary>
		/// Resizes the given window slightly, which forces a repaint.
		/// </summary>
		/// <param name="handle">
		/// </param>
		private static void JiggleWindow(
			IntPtr handle)
		{
			RECT lpRect = new RECT();
			GetWindowRect(handle, ref lpRect);

			int L = lpRect.left;
			int T = lpRect.top;
			int W = lpRect.right - lpRect.left;
			int H = lpRect.bottom - lpRect.top;

			int flags = SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER;
			SetWindowPos(handle, new IntPtr(0), L, T, W, H + 1, flags);
			SetWindowPos(handle, new IntPtr(0), L, T, W, H, flags);

			return;
		}

		#endregion

		#region Visio Events

		/// <summary>
		///	This method is an implementation of the IVisEventProc.VisEventProc method.
		/// </summary>
		/// <param name="eventCode">
		///	Event code of the event that fired
		///	</param>
		/// <param name="source">	
		///	Reference to source of the event
		///	</param>
		/// <param name="eventID">
		///	Unique identifier of the event that fired
		///	</param>
		/// <param name="eventSequenceNumber">
		///	Relative position of the event in the event list
		///	</param>
		/// <param name="subject">
		///	Reference to the subject of the event
		///	</param>
		/// <param name="moreInformation">
		///	Additional information for the event
		///	</param>
		/// <returns>
		///	Empty object.
		///	</returns>
		/// <seealso cref="IVisEventProc">
		///	The Visio application ignores the 
		///	return value unless the event is a query event. In the case of a
		///	query event, a return value of true means cancel the action and a
		///	return value of false means do not cancel the action.
		/// </seealso>
		object Visio.IVisEventProc.VisEventProc(
			short	nEventCode,
			object	pSourceObj,
			int		nEventID,
			int		nEventSeqNum,
			object	pSubjectObj, 
			object	vMoreInfo) 
		{
			object returnValue = false;		// always false unless true needed to cancel a QueryCancel event

			try 
			{
                Visio.Window subjectWindow = pSubjectObj as Visio.Window;
				// Determine which event fired. Only events that have been
				// previously set up using the AddAdvise method should be
				// handled.
				switch (nEventCode) 
				{
					// BeforeWindowClosed
					case ((short)Visio.VisEventCodes.visEvtDel + (short)Visio.VisEventCodes.visEvtWindow):
					{
                        OnBeforeWindowClosed(subjectWindow);
						break;
					}
				}
			}
			catch(System.Runtime.InteropServices.COMException ex) 
			{
                throw new NrtmException("VisEventProc", ex);
            }
            catch (NrtmException ex)
            {
                //Exception from sub function. Pass on to entry point
                throw new NrtmException("VisEventProc", ex.InnerException);
            }
            catch (Exception ex)
            {
                //Exception from current function
                throw new NrtmException("VisEventProc", ex);
            }

			// return result to Visio, ignored except for QueryCancel events
			return returnValue;
		}


		/// <summary>
		/// Handler for the OnBeforeWindowClosed event.
		/// </summary>
		/// <param name="visioWindow">
		/// The visio window that is about to close.
		/// </param>
		/// <remarks>
		/// A better implementation would be to sink to the QueryCancelBeforeWindowClose event but
		/// this event does not get fired for windows of type AnchorBarAddon.
		/// </remarks>
		private void OnBeforeWindowClosed(
			Visio.Window visioWindow)
		{
			if (Terminate != null)
			{
				Terminate();
			}
		
			// destroy this window
			DestroyWindow();

			// default return
			return;
		}

		#endregion
	}
}
